# --------------- < import > ---------------
from chiori import Config, Logger, Event
from libs.vk import convert_profiles

import asyncio
import aiohttp
import json
import re
import inspect
# --------------- < import > ---------------
chat_const = int(2E9)


class VkBackend:
	def __init__(self):
		super(VkBackend, self).__init__()
		self.ver = "0.1.1"
		self.version = "5.130"
		self.backend_type = ["vk"]
		self.token = ""
		self.api_url = "https://api.vk.com/method/{{}}?access_token={}&v={}" \
			.format(self.token, self.version)
		self.naive_cache = {}

	async def request(self, method, **args):
		"""отправка API запроса
		:param method: метод API
		:param args: доп параметры запроса
		:return: ответ"""
		if 'hide' in args:
			hide = args['hide']
			del args['hide']
		else:
			hide = True

		url = self.api_url.format(method)
		data = {}
		for k, v in args.items():
			if v:
				data[k] = v

		async with aiohttp.ClientSession() as sess:
			async with sess.post(url, data=data) as response:
				raw_respose_text = await response.text()

		raw_response = json.loads(raw_respose_text)
		if 'error' in raw_response:
			response = {'error':True, 'response':raw_response['error']}
			err = response["response"]

			rp = ''
			for x in err["request_params"]:
				rp += f' {self.l.lred}{x["key"]}{self.l.reset}: {x["value"]}'

			self.l.log(f'{self.l.lyellow}[{err["error_code"]}] {self.l.yellow}{err["error_msg"]} {self.l.reset}[{rp}]', method)
			return response

		response = {'error':False, 'response':raw_response['response']}
		# self.l.log(inspect.stack()[1][3], 'request <')
		if hide:
			self.l.log(method, 'c')
		else:
			self.l.log(str(response["response"]), f'API {method} <c>')
		return response

	def decorateMessage(self, event, message):
		message = str(message)
		decorators = {'<e>':'<ошибка>', '<c>':'<выполнено>',
					  '<m>':'<непонимание>'}
		for k, v in decorators.items():
			message = message.replace(k, v)

		data = ''
		if event.get('to.is_chat'):
			nick = event.get('from.username') or event.get('from.first_name')
			data += f"{nick}: "

		return f'{data}{message}'

	async def message(self, event, message, **params):
		"""Send message to target peer_id with parameters."""
		if event:
			message = self.decorateMessage(event, message)

		if 'attachment' in params:
			if isinstance(params['attachment'], dict):
				params['attachment'] = [params['attachment']]

			if isinstance(params['attachment'], (list, tuple)):
				new_attachment = ""

				for a in params['attachment']:
					if isinstance(a, dict):
						new_attachment += f'{a["type"]}{a["owner_id"]}_{a["id"]}\
						{"_" + a["access_key"] if a["access_key"] else ""},'

					else:
						new_attachment += str(a)

				params['attachment'] = new_attachment

		if message or params['attachment']:
			if len(message) < 4096:
				return await self.request("messages.send",
											message=message,
											random_id = '0',
											**params)
			else:
				res = []
				for x in range(len(message)//4096+1):
					res.append(await self.request("messages.send",
								message=message[4096*x:4096*(x+1)],
								random_id = '0',
								**params))

	async def upload_file(self, upload_url, data):
		async with aiohttp.ClientSession() as sess:
			response = await sess.post(upload_url, data=data)
			await sess.close()

		if response:
			data = await response.text()
			if data:
				result = json.loads(data)
				if "error" in result:
					self.l.log(result['error'], 'e')
				else:
					return result

	async def upload_doc(self, file, peer_id=None, group_id=None,
			doctype="doc", filename=None):

		if filename is None:
			filename = "file.png"

		if peer_id is None:
			peer_id = ori_peer_id

		if isinstance(file, str):
			with open(file, "rb") as o:
				file = o.read()

		if peer_id:
			upload_data = await self.request(
				"docs.getMessagesUploadServer", peer_id=peer_id, type=doctype)

		else:
			upload_data = await self.request(
				"docs.getWallUploadServer",
				group_id=group_id or self.app.storage.config.config.group_id)

		if "upload_url" not in upload_data["response"]:
			return None

		upload_url = upload_data["response"]["upload_url"]

		data = aiohttp.FormData()
		data.add_field("file", file, filename=filename)

		result = await self.upload_file(upload_url, data)

		if not result:
			return None

		attachments = await self.request(
			"docs.save", **result
		)

		if not 'response' in attachments:
			return None

		return self.convert_to_attachment(
			attachments["response"][0], "doc")

	async def upload_photo(self, file=None, bin_file=None, peer_id=None):
		if file and isinstance(file, str):
			with open(file, "rb") as o:
				file = o.read()
		elif bin_file:
			file = bin_file

		upload_data = await self.request(
			"photos.getMessagesUploadServer", group_id=self.group_id)

		if "upload_url" not in upload_data["response"]:
			return None

		upload_url = upload_data["response"]["upload_url"]

		data = aiohttp.FormData()
		data.add_field("photo", file, filename="image.png")

		result = await self.upload_file(upload_url, data)

		if not result:
			return None

		attachments = await self.request(
			"photos.saveMessagesPhoto", **result)

		if not 'response' in attachments:
			return None

		return self.convert_to_attachment(
			attachments["response"][0], "photo"
		)

	def convert_to_attachment(self, attachment, attachment_type=None):
		if "type" in attachment and attachment["type"] in attachment:
			body = attachment[attachment["type"]]
			attachment_type = attachment["type"]
		else:
			body = attachment

		if "sizes" in body:
			m_s_ind = -1
			m_s_wid = 0

			for i, size in enumerate(body["sizes"]):
				if size["width"] > m_s_wid:
					m_s_wid = size["width"]
					m_s_ind = i

			link = body["sizes"][m_s_ind]["url"]  # src

		elif "url" in body:
			link = body["url"]

		else:
			link = None

		return {'type':attachment_type, 'id':body.get("id"),
				'access_key': body.get("access_key"),
				'owner_id':body.get('owner_id'),
				'link':link, 'rawattach':attachment}

	async def resolveScreenName(self, screen_name):  # pragma: no cover
		if screen_name in self.naive_cache:
			return self.naive_cache[screen_name]

		result = await self.request(
			"utils.resolveScreenName",
			screen_name=screen_name
		)

		self.naive_cache[screen_name] = result

		return result

	def setLevel(self, event, config):
		uid = str(event.get('from.id'))
		if uid in config['admins']:
			event.model['level'] = 10
		elif str(event.get('to.id')) in config['banned_chats']:
			event.model['level'] = 0
		elif uid in config['banned']:
			event.model['level'] = 0

		return event

	async def is_admin(self, event, user_id):
		temp = await self.request('messages.getConversationMembers',
			peer_id=event.get('to.id'))
		if not temp['error']:
			for u in temp['response']['items']:
				if str(u['member_id']) == user_id:
					if 'is_admin' in u:
						return True
					break

	async def convert_to_message(self, event):
		if event["type"] != "message_new":
			return None

		obj = event["object"]['message']

		if obj.get('from_id', -1) < 0:
			return None

		text = obj["text"]

		if "conversation_message_id" in obj:
			cursor = 0
			new_text = ""

			for m in re.finditer(r"\[(.+?)\|.+?\]", text):
				resp = await self.resolveScreenName(m.group(1))
				new_text += text[cursor : m.start()]
				cursor = m.end()

				if not resp['response'] or resp['response']["object_id"] == event["group_id"]:
					continue

				new_text += text[m.start() : m.end()]
			new_text += text[cursor :]
			text = new_text.lstrip()

		# создание модели события
		e = Event()

		# бот/пользователь администратор чата

		temp = await self.request('messages.getConversationMembers',
			peer_id=obj.get('peer_id'),
			fields="bdate, online, last_seen, has_mobile")

		if not temp['error']:
			e.set('to.is_admin', True)
			e.set('to.profiles', convert_profiles(
				temp['response']['profiles'],
				temp['response']['items']))
			e.set('from.is_admin', e.get(f'to.profiles.{obj.get("from_id")}.is_admin', False))

			# описание пользователя
			if obj.get('from_id') > 0:
				if str(obj.get("from_id")) in e.get('to.profiles'):
					e.set('from', e.get(f'to.profiles.{obj.get("from_id")}'))
				else:
					res = await self.request('users.get', user_ids=obj.get('from_id'))
					if not res['error']:
						e.set('from', res["response"][0])
			else:
				e.set('from.is_mechanism', True)
		elif obj.get('from_id') > 0:
			res = await self.request('users.get', user_ids=obj.get('from_id'))
			if not res['error']:
				e.merge({'from':res["response"][0]})

		e.set('from.id', obj.get("from_id"))


		# описание чата
		e.set('to.id', obj.get("peer_id"))
		if int(obj.get("from_id")) != int(obj.get("peer_id")):
			e.set("to.is_chat", True)

		# прочее
		e.set('id', obj.get("conversation_message_id"))
		e.set('attachs', tuple(self.convert_to_attachment(a) for a in obj["attachments"]))
		e.set('date', obj.get('date'))
		e.set('text', text)
		e.set('action', obj.get('action', {}))
		e.set('raw', event)

		#  описание пересланного сообщения
		if 'reply_message' in obj:
			reply = obj['reply_message']

			temp = {}
			temp['text'] = reply.get('text')
			temp['attachs'] = tuple(self.convert_to_attachment(a) for a in reply["attachments"])
			temp['from_id'] = reply.get('from_id')

			e.set('reply', reply)

		return e
